package com.lifengdi.qiankun.common.oss;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.*;
import com.lifengdi.qiankun.common.enums.AccessControlListEnum;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;

/**
 * 阿里云OSS客户端
 *
 * @author: 李锋镝
 * @date: 2020-08-05 14:21
 */
@Component
public class ALiYunOSSClient {

    private static Logger logger = LoggerFactory.getLogger(ALiYunOSSClient.class);

    /**
     * accessKeyId
     */
    @Value("${oss.ali.access.key.id:#{null}}")
    private String accessKeyId;

    /**
     * accessKeySecret
     */
    @Value("${oss.ali.access.key.secret:#{null}}")
    private String accessKeySecret;

    /**
     * 过期时间 默认半个小时
     */
    @Value("${oss.ali.access.expire.time:1800000}")
    private Long expireTime;

    /**
     * Endpoint 按照Region实际情况填写。
     * <p>eg:</p>
     * <p>杭州：oss-cn-hangzhou.aliyuncs.com</p>
     * <p>北京：oss-cn-beijing.aliyuncs.com</p>
     */
    @Value("${oss.ali.endpoint:#{null}}")
    private String endpoint;

    /**
     * 默认bucket的名称
     */
    @Value("${oss.ali.bucket.name:#{null}}")
    private String bucketName;

    /**
     * 默认的domain
     */
    @Value("${oss.domain:#{null}}")
    private String ossDomain;

    private ALiYunOSSClient() {
    }

    public ALiYunOSSClient(String accessKeyId, String accessKeySecret, String endpoint) {
        this.accessKeyId = accessKeyId;
        this.accessKeySecret = accessKeySecret;
        this.endpoint = endpoint;
    }

    public ALiYunOSSClient(String accessKeyId, String accessKeySecret, String endpoint, String bucketName, String ossDomain) {
        this.accessKeyId = accessKeyId;
        this.accessKeySecret = accessKeySecret;
        this.endpoint = endpoint;
        this.bucketName = bucketName;
        this.ossDomain = ossDomain;
    }


    public OSS buildOSS() {
        Objects.requireNonNull(endpoint, "请指定Endpoint");
        Objects.requireNonNull(accessKeyId, "请配置accessKeyId");
        Objects.requireNonNull(accessKeySecret, "请配置accessKeySecret");
        return new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
    }

    public OSS buildDefaultOSS() {
        Objects.requireNonNull(endpoint, "请指定Endpoint");
        Objects.requireNonNull(accessKeyId, "请配置accessKeyId");
        Objects.requireNonNull(accessKeySecret, "请配置accessKeySecret");
        Objects.requireNonNull(bucketName, "请配置bucketName");
        Objects.requireNonNull(ossDomain, "请配置ossDomain");
        return new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
    }

    /**
     * 获取文件在阿里云的完整URL（包含域名）
     *
     * @param ossDomain 域名
     * @param path      文件在阿里云的子路径
     * @param filePath  文件路径
     * @return URL
     */
    public String getFullUrl(String ossDomain, String path, String filePath) {
        File file = new File(filePath);
        String fileName = file.getName();
        return ossDomain + path + fileName;
    }

    /**
     * 获取文件在阿里云的完整URL（包含域名）
     *
     * @param path     文件在阿里云的子路径
     * @param filePath 文件路径
     * @return URL
     */
    public String getFullUrl(String path, String filePath) {
        File file = new File(filePath);
        String fileName = file.getName();
        path = getKeyPrefix(path);
        return ossDomain + path + fileName;
    }

    /**
     * 上传至默认bucket
     *
     * @param path     子路径。
     * @param filePath 文件路径。
     */
    public void upload(String path, String filePath) {
        upload(bucketName, path, filePath, null, null, null);
    }

    /**
     * 上传至默认bucket
     *
     * @param path     子路径。
     * @param filePath 文件路径。
     * @return 文件在阿里云的URL
     */
    public String uploadWithReturn(String path, String filePath) {
        upload(bucketName, path, filePath, null, null, null);
        return getFullUrl(path, filePath);
    }

    /**
     * 上传。
     *
     * @param bucketName 存储空间名称。
     * @param path       子路径。
     * @param filePath   文件路径。
     * @return 文件在阿里云的URL
     */
    public String uploadWithReturn(String bucketName, String path, String filePath) {
        upload(bucketName, path, filePath, null, null, null);
        return getFullUrl(path, filePath);
    }

    /**
     * 上传。
     *
     * @param bucketName 存储空间名称。
     * @param path       子路径。
     * @param filePath   文件路径。
     * @param acl        文件的访问权限（若指定的存储桶不存在，则创建的存储桶的权限默认为第一个放入的文件的权限），具体参见{@link AccessControlListEnum}
     * @return 文件在阿里云的URL
     */
    public String uploadWithReturn(String bucketName, String path, String filePath, AccessControlListEnum acl) {
        upload(bucketName, path, filePath, null, null, acl);
        return getFullUrl(path, filePath);
    }

    /**
     * 上传。
     *
     * @param bucketName  存储空间名称。
     * @param path        子路径。
     * @param filePath    文件路径。
     * @param urlSuffix   需要覆盖的URL。
     * @param contentType 文件访问的contentType，可以传null，默认为application/octst-stream
     * @param acl         文件的访问权限（若指定的存储桶不存在，则创建的存储桶的权限默认为第一个放入的文件的权限），具体参见{@link AccessControlListEnum}
     * @return 文件在阿里云的URL
     */
    public String uploadWithReturn(String bucketName, String path, String filePath, String urlSuffix, String contentType, AccessControlListEnum acl) {
        upload(bucketName, path, filePath, urlSuffix, contentType, acl, buildOSS());
        return getFullUrl(path, filePath);
    }

    /**
     * 上传。
     *
     * @param bucketName  存储空间名称。
     * @param path        子路径。
     * @param filePath    文件路径。
     * @param urlSuffix   需要覆盖的URL。
     * @param contentType 文件访问的contentType，可以传null，默认为application/octst-stream
     * @param acl         文件的访问权限（若指定的存储桶不存在，则创建的存储桶的权限默认为第一个放入的文件的权限），具体参见{@link AccessControlListEnum}
     * @param ossClient   ossClient
     * @return 文件在阿里云的URL
     */
    public String uploadWithReturn(String bucketName, String path, String filePath, String urlSuffix, String contentType, AccessControlListEnum acl, OSS ossClient) {
        upload(bucketName, path, filePath, urlSuffix, contentType, acl, ossClient);
        return getFullUrl(path, filePath);
    }

    /**
     * 上传。
     *
     * @param bucketName 存储空间名称。
     * @param path       子路径。
     * @param filePath   文件路径。
     */
    public void upload(String bucketName, String path, String filePath) {
        upload(bucketName, path, filePath, null, null, null);
    }

    /**
     * 上传。
     *
     * @param bucketName 存储空间名称。
     * @param path       子路径。
     * @param filePath   文件路径。
     * @param acl        文件的访问权限（若指定的存储桶不存在，则创建的存储桶的权限默认为第一个放入的文件的权限），具体参见{@link AccessControlListEnum}
     */
    public void upload(String bucketName, String path, String filePath, AccessControlListEnum acl) {
        upload(bucketName, path, filePath, null, null, acl);
    }

    /**
     * 上传。
     *
     * @param bucketName  存储空间名称。
     * @param path        子路径。
     * @param filePath    文件路径。
     * @param urlSuffix   需要覆盖的URL。
     * @param contentType 文件访问的contentType，可以传null，默认为application/octst-stream
     * @param acl         文件的访问权限（若指定的存储桶不存在，则创建的存储桶的权限默认为第一个放入的文件的权限），具体参见{@link AccessControlListEnum}
     */
    public void upload(String bucketName, String path, String filePath, String urlSuffix, String contentType, AccessControlListEnum acl) {
        upload(bucketName, path, filePath, urlSuffix, contentType, acl, buildOSS());
    }

    /**
     * 上传。
     *
     * @param bucketName  存储空间名称。
     * @param path        子路径。
     * @param filePath    文件路径。
     * @param urlSuffix   需要覆盖的URL。
     * @param contentType 文件访问的contentType，可以传null，默认为application/octst-stream
     * @param acl         文件的访问权限（若指定的存储桶不存在，则创建的存储桶的权限默认为第一个放入的文件的权限），具体参见{@link AccessControlListEnum}
     * @param ossClient   ossClient
     */
    public void upload(String bucketName, String path, String filePath, String urlSuffix, String contentType, AccessControlListEnum acl, OSS ossClient) {
        try {
            // 判断Bucket是否存在。
            if (!ossClient.doesBucketExist(bucketName)) {
                logger.info("您的Bucket不存在，创建Bucket：" + bucketName + "。");
                // 创建Bucket。
                CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName);
                // 设置bucket权限为公共读，默认是私有读写.
                if (Objects.nonNull(acl)) {
                    createBucketRequest.setCannedACL(CannedAccessControlList.parse(acl.toString()));
                } else {
                    createBucketRequest.setCannedACL(CannedAccessControlList.PublicRead);
                }
                ossClient.createBucket(createBucketRequest);
            }

            // 通过path来获取key的前缀。
            String keyPrefix = getKeyPrefix(path);

            // 设置存储类型与访问权限
            ObjectMetadata objectMetadata = new ObjectMetadata();
            ;
            if (!StringUtils.isEmpty(contentType)) {
                objectMetadata.setContentType(contentType);
            }
            if (Objects.nonNull(acl)) {
                objectMetadata.setObjectAcl(CannedAccessControlList.parse(acl.toString()));
            } else {
                objectMetadata.setObjectAcl(CannedAccessControlList.Default);
            }

            // 把字符串存入OSS，Object的名称为firstKey。
            File file = new File(filePath);
            _upload(ossClient, bucketName, urlSuffix, keyPrefix, objectMetadata, file, file);
        } catch (Exception e) {
            logger.error("上传阿里云失败", e);
        } finally {
            ossClient.shutdown();
        }
    }

    /**
     * 上传文件
     *
     * @param ossClient      OSS
     * @param bucketName     bucketName
     * @param urlSuffix      需要覆盖的URL
     * @param keyPrefix      阿里云中存储文件的前缀
     * @param objectMetadata 设置存储类型与访问权限
     * @param file           文件
     * @param workRoot       当前工作的根目录
     */
    private void _upload(OSS ossClient, String bucketName, String urlSuffix, String keyPrefix, ObjectMetadata objectMetadata, File file, File workRoot) {
        if (file.isDirectory()) {
            File[] fileList = file.listFiles();
            if (Objects.isNull(fileList) || fileList.length <= 0) {
                return;
            }
            // 若是文件夹，循环上传文件夹下所有文件
            for (File subFile : fileList) {
                String currentDirectoryName = "";
                if (!file.equals(workRoot)) {
                    currentDirectoryName = file.getName() + "/";
                }
                _upload(ossClient, bucketName, urlSuffix, keyPrefix + currentDirectoryName, objectMetadata, subFile, workRoot);
            }
        } else {
            if (StringUtils.equals("index.html", file.getName())) {
                ossClient.putObject(bucketName, keyPrefix + urlSuffix, file, objectMetadata);
            } else {
                ossClient.putObject(bucketName, keyPrefix + file.getName(), file, objectMetadata);
            }
        }
    }

    /**
     * 根据子路径来获取key前缀。
     *
     * @param path 子路径。
     * @return String
     */
    private String getKeyPrefix(String path) {
        String keyPrefix = "";
        if (StringUtils.isNotBlank(path)) {
            path = formatKey(path);
            if (StringUtils.endsWith(path, "/")) {
                keyPrefix = path;
            } else {
                keyPrefix = path + "/";
            }
        }
        return keyPrefix;
    }

    private String formatKey(String path) {
        if (StringUtils.startsWith(path, "/")) {
            path = path.replaceFirst("/", "");
            // 防止开头出现多个/
            path = formatKey(path);
        }
        return path;
    }

    /**
     * 生成以GET方法访问的签名URL(默认过期时间)
     * <p>可以通过<code>oss.ali.access.expire.time</code>配置默认过期时间</p>
     *
     * @param bucketName 存储桶
     * @param key        文件在阿里云的key
     * @return URL
     */
    public String generatePresignedUrl(String bucketName, String key) {
        return generatePresignedUrl(bucketName, key, expireTime);
    }

    /**
     * 生成以GET方法访问的签名URL（指定过期时间）
     *
     * @param bucketName 存储桶
     * @param key        文件在阿里云的key
     * @param expireTime 过期时间(毫秒)
     * @return URL
     */
    public String generatePresignedUrl(String bucketName, String key, Long expireTime) {
        Objects.requireNonNull(expireTime);
        OSS ossClient = buildOSS();
        try {
            Date expiration = new Date(new Date().getTime() + expireTime);
            key = formatKey(key);
            // 生成以GET方法访问的签名URL，访客可以直接通过浏览器访问相关内容。
            URL url = ossClient.generatePresignedUrl(bucketName, key, expiration);
            return url.toString();
        } catch (Exception e) {
            logger.error("生成阿里云临时访问URL失败", e);
        } finally {
            ossClient.shutdown();
        }
        return null;
    }

    /**
     * 单个删除文件
     *
     * @param bucketName bucketName
     * @param key        文件在阿里云的key
     */
    public void delete(String bucketName, String key) {
        OSS ossClient = buildOSS();
        try {
            ossClient.deleteObject(bucketName, key);
        } catch (Exception e) {
            logger.error("阿里云OSS删除文件失败", e);
        } finally {
            ossClient.shutdown();
        }
    }

    /**
     * 批量删除文件
     *
     * @param bucketName bucketName
     * @param keys       文件在阿里云的key
     * @return 返回删除成功的文件列表。
     */
    public List<String> deleteBatch(String bucketName, List<String> keys) {
        if (CollectionUtils.isEmpty(keys)) {
            return null;
        }

        OSS ossClient = buildOSS();
        try {
            int size = keys.size();
            if (size > 1000) {
                // 记录删除成功的文件列表
                List<String> result = new ArrayList<>(size);
                int begin = 0, end = 0, batch = 1000;
                List<String> keyList;
                do {
                    // 每次最多删除1000条
                    end += batch;
                    if (end > size) {
                        end = size;
                    }
                    keyList = keys.subList(begin, end);
                    result.addAll(_deleteBatch(bucketName, keyList, ossClient));
                    begin = end;
                } while (end != size);
                return result;
            } else {
                return _deleteBatch(bucketName, keys, ossClient);
            }
        } catch (Exception e) {
            logger.error("阿里云OSS删除文件失败", e);
        } finally {
            ossClient.shutdown();
        }
        return null;
    }

    public String getDefaultBucketName() {
        return bucketName;
    }

    public String getDefaultOssDomain() {
        return ossDomain;
    }

    private List<String> _deleteBatch(String bucketName, List<String> keys, OSS ossClient) {
        DeleteObjectsResult deleteObjectsResult = ossClient.deleteObjects(new DeleteObjectsRequest(bucketName).withKeys(keys));
        return deleteObjectsResult.getDeletedObjects();
    }

    /**
     * 下载文件（指定域名和bucket）
     *
     * @param fileUrl       文件URL
     * @param localFilePath 文件本地存储路径
     * @param ossDomain     ossDomain
     * @param bucketName    bucketName
     * @return 本地文件路径
     */
    public String downloadFile(String fileUrl, String localFilePath, String ossDomain, String bucketName) {
        OSS oss = buildOSS();
        String objectName = fileUrl.replaceFirst(ossDomain, "");
        return downloadFile(objectName, localFilePath, bucketName, oss);
    }

    /**
     * 下载文件（指定bucket）
     *
     * @param fileUrl       文件URL
     * @param localFilePath 文件本地存储路径
     * @param bucketName    bucketName
     * @return 本地文件路径
     */
    public String downloadFile(String fileUrl, String localFilePath, String bucketName) {
        OSS oss = buildOSS();
        String objectName = getPath(fileUrl, false);
        return downloadFile(objectName, localFilePath, bucketName, oss);
    }

    /**
     * 下载文件(默认bucket)
     *
     * @param fileUrl       文件URL
     * @param localFilePath 文件本地存储路径
     * @return 本地文件路径
     */
    public String downloadFile(String fileUrl, String localFilePath) {
        OSS oss = buildOSS();
        String objectName = getPath(fileUrl, false);
        return downloadFile(objectName, localFilePath, bucketName, oss);
    }

    /**
     * 下载文件
     *
     * @param objectName    文件在阿里云的路径
     * @param localFilePath 文件本地存储路径
     * @param bucketName    bucketName
     * @param ossClient     OSS
     * @return 本地文件路径
     */
    public String downloadFile(String objectName, String localFilePath, String bucketName, OSS ossClient) {

        try {
            // 下载OSS文件到本地文件。如果指定的本地文件存在会覆盖，不存在则新建。
            File file = new File(localFilePath);
            if (!file.isDirectory()) {
                File parentFile = file.getParentFile();
                if (!parentFile.exists()) {
                    parentFile.mkdirs();
                }
            }
            ossClient.getObject(new GetObjectRequest(bucketName, objectName), file);
            return file.getPath();
        } finally {
            // 关闭OSSClient。
            ossClient.shutdown();
        }

    }

    public static String getPath(String fileUrl, boolean addEnd) {
        if (fileUrl.startsWith("http://")) {
            fileUrl = fileUrl.substring(7);
        }
        if (fileUrl.startsWith("https://")) {
            fileUrl = fileUrl.substring(8);
        }
        String path = "";
        // 处理域名和url 路径。
        int firstIndexOf = fileUrl.indexOf("/");
        if (firstIndexOf != -1) {
            path = fileUrl.substring(firstIndexOf + 1);
        }
        if (addEnd && !path.endsWith("/")) {
            path = path + "/";
        }
        return path;
    }

}
